package com.kamistoat.netty.netty.chat;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Netty群聊服务端 Handler。
 * 之前在NIO群聊中，由于手动在Selector中处理事件，所以当监听到新连接事件时，手动将新连接的Channel保存起来，便于之后的转发操作。
 * 而在Netty中，新连接会被自动创建Channel并注册，但是可以通过SimpleChannelInboundHandler顶层父类中的方法来获取这些时间结点
 */
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    // 用一个ChannelGroup保存所有的连接Channel
    // 1. 该变量必须是static，因为在任何连接的handler中都应该是相同的一个。
    //  当然也可以做在缓存中，或者以 Bean的形式存在(那就得是在Spring环境中了)
    // 2. GlobalEventExecutor.INSTANCE 是全局的事件执行器。如果指定该执行器，Netty能够在Channel生命周期中自动接管某些结点时的ChannelGroup操作
    //  如果指定执行器，则ChannelGroup必须全局手操
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    // 时间格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 从 ChannelAdaptHandler中继承来的方法
     * 该方法是整个Pipeline中第一个被执行的Handler。当BossGroup监听到新连接事件并创建Channel后，首次调用channel.pipeline().addLast()后方法调用。
     * 它表示当前channel中已经成功添加了一个Handler
     * <p>
     * Gets called after the ChannelHandler was added to the actual context and it's ready to handle events.
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel curChannel = ctx.channel();
        System.out.println(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "正在建立连接\n");
    }

    /**
     * 当一个Channel与WorkGroup中的一个NIOEventLoop建立绑定关系时被调用。
     * 晚于 handlerAdded
     */
//    @Override
//    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//        super.channelRegistered(ctx);
//    }

    /**
     * 当一个Channel处于Active状态
     * 该事件表示该channel.pipeline()已经添加完所有的Handler，且BossGroup已经将其分配到WorkGroup中的一个 NIOEventLoop上，整个连接已完全激活并准备好处理事件
     * 晚于 channelRegistered
     * <p>
     * The Channel of the ChannelHandlerContext is now active
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel curChannel = ctx.channel();
        // 将当前channel建立连接的信息转发给其他在线的客户端。之前在NIO中还需要遍历Channel集合，但是用Netty库类直接可以全体发送，类似流操作
        ChatServerHandler.channelGroup.writeAndFlush(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "已建立连接\n");
        // 再将当前channel即新连接的channel添加到ChannelGroup中进行管理
        ChatServerHandler.channelGroup.add(curChannel);
        System.out.println(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "已建立连接\n");
    }

    /**
     * 当Channel处于读就绪时调用该事件。相当于Client向Server发送数据
     * 晚于 channelActive
     */
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
//    }


    /**
     * 当Channel由读就绪状态转为空闲状态时调用该事件。表明Server已经读取完毕由Client发送的数据。
     * 该事件可用于执行 Server回复Client的行为
     * 晚于 channelRead
     */
//    @Override
//    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//        super.channelReadComplete(ctx);
//    }


    /**
     * 当Channel被关闭时(执行channel.close())调用该事件。
     * 晚于 channelReadComplete
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel curChannel = ctx.channel();
        ChatServerHandler.channelGroup.remove(curChannel);
        ChatServerHandler.channelGroup.writeAndFlush(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "已关闭连接\n");
    }


    /**
     * 当与该channel对应的NIOEventLoop移除掉对这条channel的处理时调用该事件
     * 晚于 channelInactive
     */
//    @Override
//    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
//        super.channelUnregistered(ctx);
//    }


    /**
     * 当该channel.pipeline()上的所有handler都被移除，且对应的NIOEventLoop移除掉对该channel的处理时触发
     * 是整个Pipeline中最后调用的事件，代表一个channel的生命终止
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel curChannel = ctx.channel();
        System.out.println(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "已关闭连接\n");
        // 实际上当触发 handlerRemoved事件时，会自动从ChannelGroup中移除当前Channel。但前提是在创建ChannelGroup时指定全局事件执行器
    }

    /**
     * 底层类 SimpleChannelInboundHandler 的read-write方法。直接在这里做，而没有使用生命周期结点事件
     * 该handler将处理String类型的数据。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        Channel curChannel = channelHandlerContext.channel();
        ChatServerHandler.channelGroup.forEach(tempChannel -> {
            if (!tempChannel.equals(curChannel)) {
                tempChannel.writeAndFlush(sdf.format(new Date()) + ": [客户端] " + curChannel.remoteAddress() + "说: " + msg + "\n");
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
